package com.xnx.bincker.leader;

import com.xnx.bincker.leader.core.annotation.Describe;
import com.xnx.bincker.leader.domain.Role;
import com.xnx.bincker.leader.utils.StringUtils;
import lombok.Getter;
import lombok.Setter;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.junit.Before;
import org.junit.Test;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;

import javax.persistence.Entity;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 代码生成器
 * 使用velocity渲染模板生成代码
 * @author bincker
 */
public class CodeGenerator {
    private static final String TEMPLATE_PATH = "classpath:code-template";
    @Getter
    private VelocityContext context;
    @Setter
    private BiConsumer<VelocityContext, String> renderBeforeListener;

    public void generate() throws IOException {
        final String javaResourceDir = "src/main/java/";
        final String baseJavaDir = javaResourceDir + "com/xnx/bincker/web/";
        final String controllerDir = baseJavaDir + "controller/";
        final String baseTemplateDir = "src/main/resources/templates/";
        Class entityClass = Role.class;
//        绑定实体类
        formEntity(entityClass).end();
//        渲染前做的操作
        renderBeforeListener = (context,filePath)->{
//            如果是生成类
            if(filePath.startsWith(javaResourceDir)){
//                生成包名
                context.put("package",
                        StringUtils.getFilePath(filePath.replace(javaResourceDir, ""))
                                .replaceAll(File.separator, ".")
                );
//                生成类名(去掉后缀的文件名)
                context.put("className", StringUtils.getFilename(StringUtils.stripFilenameExtension(filePath)));
            }
//            如果是Controller或者是HTML页面
            if(filePath.startsWith(controllerDir)){
//                URL生成
                String requestMapping = StringUtils.getFilePath(filePath.replace(controllerDir, ""));
                if(!File.separator.equals("/")){
                    requestMapping = requestMapping.replaceAll("\\\\", "/");
                }
                requestMapping += "/" + StringUtils.separatorHumpStyle(((Map<String, Object>) context.get("entity")).get("name").toString());
                context.put("requestMapping", requestMapping);
            }
//            如果是HTML模板
            if(filePath.startsWith(baseTemplateDir)){
                context.put("V", "$");
                context.put("S", "{");
                context.put("E", "}");
//                URL生成
                String requestMapping = "/" + StringUtils.getFilePath(filePath.replace(baseTemplateDir, ""));
                if(!File.separator.equals("/")){
                    requestMapping = requestMapping.replaceAll("\\\\", "/");
                }
//                与Controller不同的是，模板路径会以实体名为目录，且以/开头
                context.put("requestMapping", requestMapping);
            }
        };
//        render("controller.java.vm", baseJavaDir+"controller/admin/system");
        render("list.html.vm", baseTemplateDir+"admin/system/role/list.html");
    }

    @Before
    public void before() throws FileNotFoundException {
        Velocity.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, ResourceUtils.getFile(TEMPLATE_PATH).getPath());
        Velocity.init();
        context = new VelocityContext();
//        设置工具
        context.put("UUID", UUID.randomUUID());
        context.put("StringUtils", new StringUtils());
    }

    /**
     * 从实体类获取模板内容
     */
    private EntityInfoHandler formEntity(Class entityClass){
        return new EntityInfoHandler(entityClass, this);
    }


    /**
     * 渲染模板
     * @param templatePath 模板文件路径
     * @param target 目标文件（或目录）路径，
     *               如果是目录路径则会根据实体和模板文件命名
     *               (如实体名为User模板文件为Controller.java.vm 则渲染文件名为UserController.java)
     */
    private CodeGenerator render(String templatePath, String target, String charset) throws IOException {
        if(target.endsWith(File.separator) || !StringUtils.getFilename(target).contains(".")){
            String lastName = StringUtils.firstCharUpperCase(
                    Optional.ofNullable(StringUtils.stripFilenameExtension(templatePath))
                            .orElseThrow(()->new RuntimeException("模板文件名无效"))
            );
            if(!target.endsWith(File.separator)) target += File.separator;
            target += ((Map<String,Object>) context.get("entity")).get("name") + lastName;
        }
        File targetFile = new File(target);
        File path = targetFile.getParentFile();
        if(!path.exists() || path.isFile()){
            Assert.isTrue(path.mkdirs(), "创建目录失败: path="+path.getAbsolutePath());
        }
        if(renderBeforeListener != null){
            renderBeforeListener.accept(context, targetFile.getPath());
        }
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), charset));
        Template template = Velocity.getTemplate(templatePath, charset);
        template.merge(context, writer);
        writer.flush();
        writer.close();
        return this;
    }

    /**
     * @see #render(String, String, String)
     */
    private CodeGenerator render(String templatePath, String target) throws IOException{
        return render(templatePath, target, "utf-8");
    }
}

/**
 * 实体信息处理器
 */
class EntityInfoHandler{
    private CodeGenerator codeGenerator;
    private Class entityClass;
    private List<Field> fields;
    private Map<String, Object> entityInfo;
    /**
     * 属性处理器，可以设置附加的内容
     */
    private BiConsumer<Map<String, Object>, Field> fieldHandler;

    EntityInfoHandler(Class entityClass, CodeGenerator codeGenerator) {
        this.codeGenerator = codeGenerator;
        this.entityClass = entityClass;
//        过滤静态属性
        fields = Arrays
                .stream(entityClass.getDeclaredFields())
                .filter(field -> !Modifier.isStatic(field.getModifiers()))
                .collect(Collectors.toList());
        entityInfo = getClassInfo(entityClass);
    }

    /**
     * 获取Class信息
     */
    private static Map<String, Object> getClassInfo(Class entityClass){
        Map<String, Object> info = new HashMap<>();
        info.put("name", entityClass.getSimpleName());
        info.put("lowerName", StringUtils.firstCharLowerCase(entityClass.getSimpleName()));
        info.put("package", entityClass.getName());
        Describe describeAnnotation = (Describe) entityClass.getAnnotation(Describe.class);
        if(describeAnnotation != null) {
            info.put("title", describeAnnotation.title());
        }
        return info;
    }

    /**
     * 获取属性信息
     */
    private List<Map<String, Object>> getFieldsInfo(){
        return fields.stream().map(field -> {
            Map<String, Object> info = new HashMap<>();
            info.put("name", field.getName());
            for (Annotation annotation : field.getAnnotations()) {
                info.put("ant" + annotation.annotationType().getSimpleName(), annotation);
            }
            Class type = field.getType();
            if(Collection.class.isAssignableFrom(type)){
                info.put("multiple", true);
                type = getFieldGenericity(field);
            }
            info.put("type", getClassInfo(type));
            Describe describeAnnotation = field.getAnnotation(Describe.class);
            if(describeAnnotation != null){
                info.put("title", describeAnnotation.title());
                info.put("display", describeAnnotation.display());
            }
            if(fieldHandler != null) fieldHandler.accept(info, field);
            return info;
        }).collect(Collectors.toList());
    }

    /**
     * 获取关联的实体信息
     */
    private List<Map<String, Object>> getAssociatedEntitiesInfo(){
        return fields.stream()
                .map(field -> {
                    Class type = field.getType();
//                    如果是列表则取出泛型做判断
                    if(Collection.class.isAssignableFrom(type)){
                        type = getFieldGenericity(field);
                    }
                    return type;
                })
                .filter(cls -> cls.isAnnotationPresent(Entity.class))
                .map(EntityInfoHandler::getClassInfo).collect(Collectors.toList());
    }

    /**
     * 获取方法返回值的泛型
     */
    private static Class<?> getFieldGenericity(Field field){
        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
        Type[] types = parameterizedType.getActualTypeArguments();
        try {
            return Objects.requireNonNull(ClassUtils.getDefaultClassLoader()).loadClass(types[0].getTypeName());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 过滤属性
     */
    EntityInfoHandler filterField(Predicate<Field> fieldPredicate){
        fields.removeIf(field -> !fieldPredicate.test(field));
        return this;
    }

    /**
     * 通过注解过滤
     * @param annotations 注解
     */
    EntityInfoHandler filterField(Class ...annotations){
        fields.removeIf(field -> {
            for (Class annotation : annotations) {
                if(field.getAnnotation(annotation) != null){
                    return false;
                }
            }
            return true;
        });
        return this;
    }

    /**
     * 处理实体类信息
     */
    EntityInfoHandler handlerEntityInfo(Consumer<Map<String, Object>> consumer){
        consumer.accept(entityInfo);
        return this;
    }

    /**
     * 设置属性内容处理器
     */
    EntityInfoHandler handlerField(BiConsumer<Map<String, Object>, Field> handler){
        this.fieldHandler = handler;
        return this;
    }

    CodeGenerator end(String modelName){
        entityInfo.put("fields", getFieldsInfo());
        entityInfo.put("associatedEntities", getAssociatedEntitiesInfo());
        codeGenerator.getContext().put(modelName, entityInfo);
        return codeGenerator;
    }

    CodeGenerator end(){
        return end("entity");
    }
}
